#pragma once

#include "../widgets.h"
#include "basechart_layer.h"
#include "basechart_painter.h"

inline void setMarginValue(QMargins& margins, MarginSide side, int value) {
  switch (side) {
    case msLeft:
      margins.setLeft(value);
      break;
    case msRight:
      margins.setRight(value);
      break;
    case msTop:
      margins.setTop(value);
      break;
    case msBottom:
      margins.setBottom(value);
      break;
    case msAll:
      margins = QMargins(value, value, value, value);
      break;
    default:
      break;
  }
}

inline int getMarginValue(const QMargins& margins, MarginSide side) {
  switch (side) {
    case msLeft:
      return margins.left();
    case msRight:
      return margins.right();
    case msTop:
      return margins.top();
    case msBottom:
      return margins.bottom();
    default:
      break;
  }
  return 0;
}

class BaseChartCustomPlot;
class BaseChartLayoutElement;
class S_WIDGETS_EXPORT BaseChartMarginGroup : public QObject {
  Q_OBJECT
 public:
  explicit BaseChartMarginGroup(BaseChartCustomPlot* parentPlot);
  virtual ~BaseChartMarginGroup();

  // non-virtual methods:
  QList<BaseChartLayoutElement*> elements(MarginSide side) const {
    return mChildren.value(side);
  }
  bool isEmpty() const;
  void clear();

 protected:
  // non-property members:
  BaseChartCustomPlot* mParentPlot;
  QHash<MarginSide, QList<BaseChartLayoutElement*> > mChildren;

  // introduced virtual methods:
  virtual int commonMargin(MarginSide side) const;

  // non-virtual methods:
  void addChild(MarginSide side, BaseChartLayoutElement* element);
  void removeChild(MarginSide side, BaseChartLayoutElement* element);

 private:
  Q_DISABLE_COPY(BaseChartMarginGroup)

  friend class BaseChartLayoutElement;
};

class BaseChartLayout;
class S_WIDGETS_EXPORT BaseChartLayoutElement : public BaseChartLayerable {
  Q_OBJECT
  /// \cond INCLUDE_QPROPERTIES
  Q_PROPERTY(BaseChartLayout* layout READ layout)
  Q_PROPERTY(QRect rect READ rect)
  Q_PROPERTY(QRect outerRect READ outerRect WRITE setOuterRect)
  Q_PROPERTY(QMargins margins READ margins WRITE setMargins)
  Q_PROPERTY(
      QMargins minimumMargins READ minimumMargins WRITE setMinimumMargins)
  Q_PROPERTY(QSize minimumSize READ minimumSize WRITE setMinimumSize)
  Q_PROPERTY(QSize maximumSize READ maximumSize WRITE setMaximumSize)
  Q_PROPERTY(SizeConstraintRect sizeConstraintRect READ sizeConstraintRect WRITE
                 setSizeConstraintRect)
  /// \endcond
 public:
  /*!
    Defines the phases of the update process, that happens just before a replot.
    At each phase, \ref update is called with the according UpdatePhase value.
  */
  enum UpdatePhase {
    upPreparation  ///< Phase used for any type of preparation that needs to be
                   ///< done before margin calculation and layout
    ,
    upMargins  ///< Phase in which the margins are calculated and set
    ,
    upLayout  ///< Final phase in which the layout system places the rects of
              ///< the elements
  };
  Q_ENUMS(UpdatePhase)

  /*!
      Defines to which rect of a layout element the size constraints that can be
     set via \ref setMinimumSize and \ref setMaximumSize apply. The outer rect
     (\ref outerRect) includes the margins (e.g. in the case of a QCPAxisRect
     the axis labels), whereas the inner rect (\ref rect) does not.

      \see setSizeConstraintRect
  */
  enum SizeConstraintRect {
    scrInnerRect  ///< Minimum/Maximum size constraints apply to inner rect
    ,
    scrOuterRect  ///< Minimum/Maximum size constraints apply to outer rect,
                  ///< thus include layout element margins
  };
  Q_ENUMS(SizeConstraintRect)

  explicit BaseChartLayoutElement(BaseChartCustomPlot* parentPlot = nullptr);
  virtual ~BaseChartLayoutElement() Q_DECL_OVERRIDE;

  // getters:
  BaseChartLayout* layout() const { return mParentLayout; }
  QRect rect() const { return mRect; }
  QRect outerRect() const { return mOuterRect; }
  QMargins margins() const { return mMargins; }
  QMargins minimumMargins() const { return mMinimumMargins; }
  MarginSides autoMargins() const { return mAutoMargins; }
  QSize minimumSize() const { return mMinimumSize; }
  QSize maximumSize() const { return mMaximumSize; }
  SizeConstraintRect sizeConstraintRect() const { return mSizeConstraintRect; }
  BaseChartMarginGroup* marginGroup(MarginSide side) const {
    return mMarginGroups.value(side, nullptr);
  }
  QHash<MarginSide, BaseChartMarginGroup*> marginGroups() const {
    return mMarginGroups;
  }

  // setters:
  void setOuterRect(const QRect& rect);
  void setMargins(const QMargins& margins);
  void setMinimumMargins(const QMargins& margins);
  void setAutoMargins(MarginSides sides);
  void setMinimumSize(const QSize& size);
  void setMinimumSize(int width, int height);
  void setMaximumSize(const QSize& size);
  void setMaximumSize(int width, int height);
  void setSizeConstraintRect(SizeConstraintRect constraintRect);
  void setMarginGroup(MarginSides sides, BaseChartMarginGroup* group);

  // introduced virtual methods:
  virtual void update(UpdatePhase phase);
  virtual QSize minimumOuterSizeHint() const;
  virtual QSize maximumOuterSizeHint() const;
  virtual QList<BaseChartLayoutElement*> elements(bool recursive) const;

  // reimplemented virtual methods:
  virtual double selectTest(const QPointF& pos, bool onlySelectable,
                            QVariant* details = nullptr) const Q_DECL_OVERRIDE;

 protected:
  // property members:
  BaseChartLayout* mParentLayout;
  QSize mMinimumSize, mMaximumSize;
  SizeConstraintRect mSizeConstraintRect;
  QRect mRect, mOuterRect;
  QMargins mMargins, mMinimumMargins;
  MarginSides mAutoMargins;
  QHash<MarginSide, BaseChartMarginGroup*> mMarginGroups;

  // introduced virtual methods:
  virtual int calculateAutoMargin(MarginSide side);
  virtual void layoutChanged();

  // reimplemented virtual methods:
  virtual void applyDefaultAntialiasingHint(BaseChartPainter* painter) const
      Q_DECL_OVERRIDE {
    Q_UNUSED(painter)
  }
  virtual void draw(BaseChartPainter* painter) Q_DECL_OVERRIDE {
    Q_UNUSED(painter)
  }
  virtual void parentPlotInitialized(BaseChartCustomPlot* parentPlot)
      Q_DECL_OVERRIDE;

 private:
  Q_DISABLE_COPY(BaseChartLayoutElement)

  friend class BaseChartCustomPlot;
  friend class BaseChartLayout;
  friend class BaseChartMarginGroup;
};

class S_WIDGETS_EXPORT BaseChartLayout : public BaseChartLayoutElement {
  Q_OBJECT
 public:
  explicit BaseChartLayout();

  // reimplemented virtual methods:
  virtual void update(UpdatePhase phase) Q_DECL_OVERRIDE;
  virtual QList<BaseChartLayoutElement*> elements(bool recursive) const
      Q_DECL_OVERRIDE;

  // introduced virtual methods:
  virtual int elementCount() const = 0;
  virtual BaseChartLayoutElement* elementAt(int index) const = 0;
  virtual BaseChartLayoutElement* takeAt(int index) = 0;
  virtual bool take(BaseChartLayoutElement* element) = 0;
  virtual void simplify();

  // non-virtual methods:
  bool removeAt(int index);
  bool remove(BaseChartLayoutElement* element);
  void clear();

 protected:
  // introduced virtual methods:
  virtual void updateLayout();

  // non-virtual methods:
  void sizeConstraintsChanged() const;
  void adoptElement(BaseChartLayoutElement* el);
  void releaseElement(BaseChartLayoutElement* el);
  QVector<int> getSectionSizes(QVector<int> maxSizes, QVector<int> minSizes,
                               QVector<double> stretchFactors,
                               int totalSize) const;
  static QSize getFinalMinimumOuterSize(const BaseChartLayoutElement* el);
  static QSize getFinalMaximumOuterSize(const BaseChartLayoutElement* el);

 private:
  Q_DISABLE_COPY(BaseChartLayout)
  friend class BaseChartLayoutElement;
};

class S_WIDGETS_EXPORT BaseChartLayoutGrid : public BaseChartLayout {
  Q_OBJECT
  /// \cond INCLUDE_QPROPERTIES
  Q_PROPERTY(int rowCount READ rowCount)
  Q_PROPERTY(int columnCount READ columnCount)
  Q_PROPERTY(QList<double> columnStretchFactors READ columnStretchFactors WRITE
                 setColumnStretchFactors)
  Q_PROPERTY(QList<double> rowStretchFactors READ rowStretchFactors WRITE
                 setRowStretchFactors)
  Q_PROPERTY(int columnSpacing READ columnSpacing WRITE setColumnSpacing)
  Q_PROPERTY(int rowSpacing READ rowSpacing WRITE setRowSpacing)
  Q_PROPERTY(FillOrder fillOrder READ fillOrder WRITE setFillOrder)
  Q_PROPERTY(int wrap READ wrap WRITE setWrap)
  /// \endcond
 public:
  /*!
    Defines in which direction the grid is filled when using \ref
    addElement(BaseChartLayoutElement*). The column/row at which wrapping into
    the next row/column occurs can be specified with \ref setWrap.

    \see setFillOrder
  */
  enum FillOrder {
    foRowsFirst  ///< Rows are filled first, and a new element is wrapped to the
                 ///< next column if the row count would exceed \ref setWrap.
    ,
    foColumnsFirst  ///< Columns are filled first, and a new element is wrapped
                    ///< to the next row if the column count would exceed \ref
                    ///< setWrap.
  };
  Q_ENUMS(FillOrder)

  explicit BaseChartLayoutGrid();
  virtual ~BaseChartLayoutGrid() Q_DECL_OVERRIDE;

  // getters:
  int rowCount() const { return mElements.size(); }
  int columnCount() const {
    return mElements.size() > 0 ? mElements.first().size() : 0;
  }
  QList<double> columnStretchFactors() const { return mColumnStretchFactors; }
  QList<double> rowStretchFactors() const { return mRowStretchFactors; }
  int columnSpacing() const { return mColumnSpacing; }
  int rowSpacing() const { return mRowSpacing; }
  int wrap() const { return mWrap; }
  FillOrder fillOrder() const { return mFillOrder; }

  // setters:
  void setColumnStretchFactor(int column, double factor);
  void setColumnStretchFactors(const QList<double>& factors);
  void setRowStretchFactor(int row, double factor);
  void setRowStretchFactors(const QList<double>& factors);
  void setColumnSpacing(int pixels);
  void setRowSpacing(int pixels);
  void setWrap(int count);
  void setFillOrder(FillOrder order, bool rearrange = true);

  // reimplemented virtual methods:
  virtual void updateLayout() Q_DECL_OVERRIDE;
  virtual int elementCount() const Q_DECL_OVERRIDE {
    return rowCount() * columnCount();
  }
  virtual BaseChartLayoutElement* elementAt(int index) const Q_DECL_OVERRIDE;
  virtual BaseChartLayoutElement* takeAt(int index) Q_DECL_OVERRIDE;
  virtual bool take(BaseChartLayoutElement* element) Q_DECL_OVERRIDE;
  virtual QList<BaseChartLayoutElement*> elements(bool recursive) const
      Q_DECL_OVERRIDE;
  virtual void simplify() Q_DECL_OVERRIDE;
  virtual QSize minimumOuterSizeHint() const Q_DECL_OVERRIDE;
  virtual QSize maximumOuterSizeHint() const Q_DECL_OVERRIDE;

  // non-virtual methods:
  BaseChartLayoutElement* element(int row, int column) const;
  bool addElement(int row, int column, BaseChartLayoutElement* element);
  bool addElement(BaseChartLayoutElement* element);
  bool hasElement(int row, int column);
  void expandTo(int newRowCount, int newColumnCount);
  void insertRow(int newIndex);
  void insertColumn(int newIndex);
  int rowColToIndex(int row, int column) const;
  void indexToRowCol(int index, int& row, int& column) const;

 protected:
  // property members:
  QList<QList<BaseChartLayoutElement*> > mElements;
  QList<double> mColumnStretchFactors;
  QList<double> mRowStretchFactors;
  int mColumnSpacing, mRowSpacing;
  int mWrap;
  FillOrder mFillOrder;

  // non-virtual methods:
  void getMinimumRowColSizes(QVector<int>* minColWidths,
                             QVector<int>* minRowHeights) const;
  void getMaximumRowColSizes(QVector<int>* maxColWidths,
                             QVector<int>* maxRowHeights) const;

 private:
  Q_DISABLE_COPY(BaseChartLayoutGrid)
};

class S_WIDGETS_EXPORT BaseChartLayoutInset : public BaseChartLayout {
  Q_OBJECT
 public:
  /*!
    Defines how the placement and sizing is handled for a certain element in a
    BaseChartLayoutInset.
  */
  enum InsetPlacement {
    ipFree  ///< The element may be positioned/sized arbitrarily, see \ref
            ///< setInsetRect
    ,
    ipBorderAligned  ///< The element is aligned to one of the layout sides, see
                     ///< \ref setInsetAlignment
  };
  Q_ENUMS(InsetPlacement)

  explicit BaseChartLayoutInset();
  virtual ~BaseChartLayoutInset() Q_DECL_OVERRIDE;

  // getters:
  InsetPlacement insetPlacement(int index) const;
  Qt::Alignment insetAlignment(int index) const;
  QRectF insetRect(int index) const;

  // setters:
  void setInsetPlacement(int index, InsetPlacement placement);
  void setInsetAlignment(int index, Qt::Alignment alignment);
  void setInsetRect(int index, const QRectF& rect);

  // reimplemented virtual methods:
  virtual void updateLayout() Q_DECL_OVERRIDE;
  virtual int elementCount() const Q_DECL_OVERRIDE;
  virtual BaseChartLayoutElement* elementAt(int index) const Q_DECL_OVERRIDE;
  virtual BaseChartLayoutElement* takeAt(int index) Q_DECL_OVERRIDE;
  virtual bool take(BaseChartLayoutElement* element) Q_DECL_OVERRIDE;
  virtual void simplify() Q_DECL_OVERRIDE {}
  virtual double selectTest(const QPointF& pos, bool onlySelectable,
                            QVariant* details = nullptr) const Q_DECL_OVERRIDE;

  // non-virtual methods:
  void addElement(BaseChartLayoutElement* element, Qt::Alignment alignment);
  void addElement(BaseChartLayoutElement* element, const QRectF& rect);

 protected:
  // property members:
  QList<BaseChartLayoutElement*> mElements;
  QList<InsetPlacement> mInsetPlacement;
  QList<Qt::Alignment> mInsetAlignment;
  QList<QRectF> mInsetRect;

 private:
  Q_DISABLE_COPY(BaseChartLayoutInset)
};

Q_DECLARE_METATYPE(BaseChartLayoutElement::UpdatePhase)
Q_DECLARE_METATYPE(BaseChartLayoutGrid::FillOrder)
Q_DECLARE_METATYPE(BaseChartLayoutInset::InsetPlacement)
